home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1992, Brian Berliner and Jeff Polk
- * Copyright (c) 1989-1992, Brian Berliner
- *
- * You may distribute under the terms of the GNU General Public License as
- * specified in the README file that comes with the CVS 1.3 kit.
- *
- * "update" updates the version in the present directory with respect to the RCS
- * repository. The present version must have been created by "checkout". The
- * user can keep up-to-date by calling "update" whenever he feels like it.
- *
- * The present version can be committed by "commit", but this keeps the version
- * in tact.
- *
- * Arguments following the options are taken to be file names to be updated,
- * rather than updating the entire directory.
- *
- * Modified or non-existent RCS files are checked out and reported as U
- * <user_file>
- *
- * Modified user files are reported as M <user_file>. If both the RCS file and
- * the user file have been modified, the user file is replaced by the result
- * of rcsmerge, and a backup file is written for the user in .#file.version.
- * If this throws up irreconcilable differences, the file is reported as C
- * <user_file>, and as M <user_file> otherwise.
- *
- * Files added but not yet committed are reported as A <user_file>. Files
- * removed but not yet committed are reported as R <user_file>.
- *
- * If the current directory contains subdirectories that hold concurrent
- * versions, these are updated too. If the -d option was specified, new
- * directories added to the repository are automatically created and updated
- * as well.
- */
-
- #include "cvs.h"
-
- #ifndef lint
- static char rcsid[] = "@(#)update.c 1.83 92/04/10";
- #endif
-
- #if __STDC__
- static int checkout_file (char *file, char *repository, List *entries,
- List *srcfiles, Vers_TS *vers_ts, char *update_dir);
- static int isemptydir (char *dir);
- static int merge_file (char *file, char *repository, List *entries,
- Vers_TS *vers, char *update_dir);
- static int scratch_file (char *file, char *repository, List * entries,
- char *update_dir);
- static Dtype update_dirent_proc (char *dir, char *repository, char *update_dir);
- static int update_dirleave_proc (char *dir, int err, char *update_dir);
- static int update_file_proc (char *file, char *update_dir, char *repository,
- List * entries, List * srcfiles);
- static int update_filesdone_proc (int err, char *repository, char *update_dir);
- static int write_letter (char *file, int letter, char *update_dir);
- static void ignore_files (List * ilist, char *update_dir);
- static void join_file (char *file, List *srcfiles, Vers_TS *vers_ts,
- char *update_dir);
- #else
- static int update_file_proc ();
- static int update_filesdone_proc ();
- static Dtype update_dirent_proc ();
- static int update_dirleave_proc ();
- static int isemptydir ();
- static int scratch_file ();
- static int checkout_file ();
- static int write_letter ();
- static int merge_file ();
- static void ignore_files ();
- static void join_file ();
- #endif /* __STDC__ */
-
- static char *options = NULL;
- static char *tag = NULL;
- static char *date = NULL;
- static char *join_rev1, *date_rev1;
- static char *join_rev2, *date_rev2;
- static int aflag = 0;
- static int force_tag_match = 1;
- static int update_build_dirs = 0;
- static int update_prune_dirs = 0;
- static int pipeout = 0;
- static List *ignlist = (List *) NULL;
-
- static char *update_usage[] =
- {
- "Usage:\n %s %s [-APQdflRpq] [-k kopt] [-r rev|-D date] [-j rev] [-I ign] [files...]\n",
- "\t-A\tReset any sticky tags/date/kopts.\n",
- "\t-P\tPrune empty directories.\n",
- "\t-Q\tReally quiet.\n",
- "\t-d\tBuild directories, like checkout does.\n",
- "\t-f\tForce a head revision match if tag/date not found.\n",
- "\t-l\tLocal directory only, no recursion.\n",
- "\t-R\tProcess directories recursively.\n",
- "\t-p\tSend updates to standard output.\n",
- "\t-q\tSomewhat quiet.\n",
- "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
- "\t-r rev\tUpdate using specified revision/tag.\n",
- "\t-D date\tSet date to update from.\n",
- "\t-j rev\tMerge in changes made between current revision and rev.\n",
- "\t-I ign\tMore files to ignore (! to reset).\n",
- NULL
- };
-
- /*
- * update is the argv,argc based front end for arg parsing
- */
- int
- update (argc, argv)
- int argc;
- char *argv[];
- {
- int c, err;
- int local = 0; /* recursive by default */
- int which; /* where to look for files and dirs */
-
- if (argc == -1)
- usage (update_usage);
-
- ign_setup ();
-
- /* parse the args */
- optind = 1;
- while ((c = gnu_getopt (argc, argv, "ApPflRQqdk:r:D:j:I:")) != -1)
- {
- switch (c)
- {
- case 'A':
- aflag = 1;
- break;
- case 'I':
- ign_add (optarg, 0);
- break;
- case 'k':
- if (options)
- free (options);
- options = RCS_check_kflag (optarg);
- break;
- case 'l':
- local = 1;
- break;
- case 'R':
- local = 0;
- break;
- case 'Q':
- really_quiet = 1;
- /* FALL THROUGH */
- case 'q':
- quiet = 1;
- break;
- case 'd':
- update_build_dirs = 1;
- break;
- case 'f':
- force_tag_match = 0;
- break;
- case 'r':
- tag = optarg;
- break;
- case 'D':
- date = Make_Date (optarg);
- break;
- case 'P':
- update_prune_dirs = 1;
- break;
- case 'p':
- pipeout = 1;
- noexec = 1; /* so no locks will be created */
- break;
- case 'j':
- if (join_rev2)
- error (1, 0, "only two -j options can be specified");
- if (join_rev1)
- join_rev2 = optarg;
- else
- join_rev1 = optarg;
- break;
- case '?':
- default:
- usage (update_usage);
- break;
- }
- }
- argc -= optind;
- argv += optind;
-
- /*
- * If we are updating the entire directory (for real) and building dirs
- * as we go, we make sure there is no static entries file and write the
- * tag file as appropriate
- */
- if (argc <= 0 && !pipeout)
- {
- if (update_build_dirs)
- (void) unlink_file (CVSADM_ENTSTAT);
-
- /* keep the CVS/Tag file current with the specified arguments */
- if (aflag || tag || date)
- WriteTag ((char *) NULL, tag, date);
- }
-
- /* look for files/dirs locally and in the repository */
- which = W_LOCAL | W_REPOS;
-
- /* look in the attic too if a tag or date is specified */
- if (tag != NULL || date != NULL)
- which |= W_ATTIC;
-
- /* call the command line interface */
- err = do_update (argc, argv, options, tag, date, force_tag_match,
- local, update_build_dirs, aflag, update_prune_dirs,
- pipeout, which, join_rev1, join_rev2, (char *) NULL);
-
- /* free the space Make_Date allocated if necessary */
- if (date != NULL)
- free (date);
-
- return (err);
- }
-
- /*
- * Command line interface to update (used by checkout)
- */
- int
- do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
- xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir)
- int argc;
- char *argv[];
- char *xoptions;
- char *xtag;
- char *xdate;
- int xforce;
- int local;
- int xbuild;
- int xaflag;
- int xprune;
- int xpipeout;
- int which;
- char *xjoin_rev1;
- char *xjoin_rev2;
- char *preload_update_dir;
- {
- int err = 0;
- char *cp;
-
- /* fill in the statics */
- options = xoptions;
- tag = xtag;
- date = xdate;
- force_tag_match = xforce;
- update_build_dirs = xbuild;
- aflag = xaflag;
- update_prune_dirs = xprune;
- pipeout = xpipeout;
-
- /* setup the join support */
- join_rev1 = xjoin_rev1;
- join_rev2 = xjoin_rev2;
- if (join_rev1 && (cp = index (join_rev1, ':')) != NULL)
- {
- *cp++ = '\0';
- date_rev1 = Make_Date (cp);
- }
- else
- date_rev1 = (char *) NULL;
- if (join_rev2 && (cp = index (join_rev2, ':')) != NULL)
- {
- *cp++ = '\0';
- date_rev2 = Make_Date (cp);
- }
- else
- date_rev2 = (char *) NULL;
-
- /* call the recursion processor */
- err = start_recursion (update_file_proc, update_filesdone_proc,
- update_dirent_proc, update_dirleave_proc,
- argc, argv, local, which, aflag, 1,
- preload_update_dir, 1);
- return (err);
- }
-
- /*
- * This is the callback proc for update. It is called for each file in each
- * directory by the recursion code. The current directory is the local
- * instantiation. file is the file name we are to operate on. update_dir is
- * set to the path relative to where we started (for pretty printing).
- * repository is the repository. entries and srcfiles are the pre-parsed
- * entries and source control files.
- *
- * This routine decides what needs to be done for each file and does the
- * appropriate magic for checkout
- */
- static int
- update_file_proc (file, update_dir, repository, entries, srcfiles)
- char *file;
- char *update_dir;
- char *repository;
- List *entries;
- List *srcfiles;
- {
- int retval;
- Ctype status;
- Vers_TS *vers;
-
- status = Classify_File (file, tag, date, options, force_tag_match,
- aflag, repository, entries, srcfiles, &vers);
- if (pipeout)
- {
- /*
- * We just return success without doing anything if any of the really
- * funky cases occur
- *
- * If there is still a valid RCS file, do a regular checkout type
- * operation
- */
- switch (status)
- {
- case T_UNKNOWN: /* unknown file was explicitly asked
- * about */
- case T_REMOVE_ENTRY: /* needs to be un-registered */
- case T_ADDED: /* added but not committed */
- retval = 0;
- break;
- case T_CONFLICT: /* old punt-type errors */
- retval = 1;
- break;
- case T_UPTODATE: /* file was already up-to-date */
- case T_NEEDS_MERGE: /* needs merging */
- case T_MODIFIED: /* locally modified */
- case T_REMOVED: /* removed but not committed */
- case T_CHECKOUT: /* needs checkout */
- retval = checkout_file (file, repository, entries, srcfiles,
- vers, update_dir);
- break;
-
- default: /* can't ever happen :-) */
- error (0, 0,
- "unknown file status %d for file %s", status, file);
- retval = 0;
- break;
- }
- }
- else
- {
- switch (status)
- {
- case T_UNKNOWN: /* unknown file was explicitly asked
- * about */
- case T_UPTODATE: /* file was already up-to-date */
- retval = 0;
- break;
- case T_CONFLICT: /* old punt-type errors */
- retval = 1;
- break;
- case T_NEEDS_MERGE: /* needs merging */
- retval = merge_file (file, repository, entries,
- vers, update_dir);
- break;
- case T_MODIFIED: /* locally modified */
- retval = write_letter (file, 'M', update_dir);
- break;
- case T_CHECKOUT: /* needs checkout */
- retval = checkout_file (file, repository, entries, srcfiles,
- vers, update_dir);
- break;
- case T_ADDED: /* added but not committed */
- retval = write_letter (file, 'A', update_dir);
- break;
- case T_REMOVED: /* removed but not committed */
- retval = write_letter (file, 'R', update_dir);
- break;
- case T_REMOVE_ENTRY: /* needs to be un-registered */
- retval = scratch_file (file, repository, entries, update_dir);
- break;
- default: /* can't ever happen :-) */
- error (0, 0,
- "unknown file status %d for file %s", status, file);
- retval = 0;
- break;
- }
- }
-
- /* only try to join if things have gone well thus far */
- if (retval == 0 && join_rev1)
- join_file (file, srcfiles, vers, update_dir);
-
- /* if this directory has an ignore list, add this file to it */
- if (ignlist)
- {
- Node *p;
-
- p = getnode ();
- p->type = FILES;
- p->key = xstrdup (file);
- (void) addnode (ignlist, p);
- }
-
- freevers_ts (&vers);
- return (retval);
- }
-
- /*
- * update_filesdone_proc () is used
- */
- /* ARGSUSED */
- static int
- update_filesdone_proc (err, repository, update_dir)
- int err;
- char *repository;
- char *update_dir;
- {
- /* if this directory has an ignore list, process it then free it */
- if (ignlist)
- {
- ignore_files (ignlist, update_dir);
- dellist (&ignlist);
- }
-
- /* Clean up CVS admin dirs if we are export */
- if (strcmp (command_name, "export") == 0)
- {
- run_setup ("%s -fr", RM);
- run_arg (CVSADM);
- (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
- }
-
- return (err);
- }
-
- /*
- * update_dirent_proc () is called back by the recursion processor before a
- * sub-directory is processed for update. In this case, update_dirent proc
- * will probably create the directory unless -d isn't specified and this is a
- * new directory. A return code of 0 indicates the directory should be
- * processed by the recursion code. A return of non-zero indicates the
- * recursion code should skip this directory.
- */
- static Dtype
- update_dirent_proc (dir, repository, update_dir)
- char *dir;
- char *repository;
- char *update_dir;
- {
- if (!isdir (dir))
- {
- /* if we aren't building dirs, blow it off */
- if (!update_build_dirs)
- return (R_SKIP_ALL);
-
- if (noexec)
- {
- /* ignore the missing dir if -n is specified */
- error (0, 0, "New directory `%s' -- ignored", dir);
- return (R_SKIP_ALL);
- }
- else
- {
- /* otherwise, create the dir and appropriate adm files */
- make_directory (dir);
- Create_Admin (dir, repository, tag, date);
- }
- }
-
- /*
- * If we are building dirs and not going to stdout, we make sure there is
- * no static entries file and write the tag file as appropriate
- */
- if (!pipeout)
- {
- if (update_build_dirs)
- {
- char tmp[PATH_MAX];
-
- (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT);
- (void) unlink_file (tmp);
- }
-
- /* keep the CVS/Tag file current with the specified arguments */
- if (aflag || tag || date)
- WriteTag (dir, tag, date);
-
- /* initialize the ignore list for this directory */
- ignlist = getlist ();
- }
-
- /* print the warm fuzzy message */
- if (!quiet)
- error (0, 0, "Updating %s", update_dir);
-
- return (R_PROCESS);
- }
-
- /*
- * update_dirleave_proc () is called back by the recursion code upon leaving
- * a directory. It will prune empty directories if needed and will execute
- * any appropriate update programs.
- */
- /* ARGSUSED */
- static int
- update_dirleave_proc (dir, err, update_dir)
- char *dir;
- int err;
- char *update_dir;
- {
- FILE *fp;
-
- /* run the update_prog if there is one */
- if (err == 0 && !pipeout && !noexec &&
- (fp = fopen (CVSADM_UPROG, "r")) != NULL)
- {
- char *cp;
- char *repository;
- char line[MAXLINELEN];
-
- repository = Name_Repository ((char *) NULL, update_dir);
- if (fgets (line, sizeof (line), fp) != NULL)
- {
- if ((cp = rindex (line, '\n')) != NULL)
- *cp = '\0';
- run_setup ("%s %s", line, repository);
- (void) printf ("%s %s: Executing '", program_name, command_name);
- run_print (stdout);
- (void) printf ("'\n");
- (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
- }
- (void) fclose (fp);
- free (repository);
- }
-
- /* Clean up CVS admin dirs if we are export */
- if (strcmp (command_name, "export") == 0)
- {
- run_setup ("%s -fr", RM);
- run_arg (CVSADM);
- (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
- }
-
- /* Prune empty dirs on the way out - if necessary */
- (void) chdir ("..");
- if (update_prune_dirs && isemptydir (dir))
- {
- run_setup ("%s -fr", RM);
- run_arg (dir);
- (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
- }
-
- return (err);
- }
-
- /*
- * Returns 1 if the argument directory is completely empty, other than the
- * existence of the CVS directory entry. Zero otherwise.
- */
- static int
- isemptydir (dir)
- char *dir;
- {
- DIR *dirp;
- struct direct *dp;
-
- if ((dirp = opendir (dir)) == NULL)
- {
- error (0, 0, "cannot open directory %s for empty check", dir);
- return (0);
- }
- while ((dp = readdir (dirp)) != NULL)
- {
- if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 &&
- strcmp (dp->d_name, CVSADM) != 0 &&
- strcmp (dp->d_name, OCVSADM) != 0)
- {
- (void) closedir (dirp);
- return (0);
- }
- }
- (void) closedir (dirp);
- return (1);
- }
-
- /*
- * scratch the Entries file entry associated with a file
- */
- static int
- scratch_file (file, repository, entries, update_dir)
- char *file;
- char *repository;
- List *entries;
- char *update_dir;
- {
- history_write ('W', update_dir, "", file, repository);
- Scratch_Entry (entries, file);
- (void) unlink_file (file);
- return (0);
- }
-
- /*
- * check out a file - essentially returns the result of the fork on "co".
- */
- static int
- checkout_file (file, repository, entries, srcfiles, vers_ts, update_dir)
- char *file;
- char *repository;
- List *entries;
- List *srcfiles;
- Vers_TS *vers_ts;
- char *update_dir;
- {
- char backup[PATH_MAX];
- int set_time, retval = 0;
- int retcode = 0;
-
- /* don't screw with backup files if we're going to stdout */
- if (!pipeout)
- {
- (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
- if (isfile (file))
- rename_file (file, backup);
- else
- (void) unlink_file (backup);
- }
-
- run_setup ("%s%s -q -r%s %s", Rcsbin, RCS_CO, vers_ts->vn_rcs,
- vers_ts->options);
-
- /*
- * if we are checking out to stdout, print a nice message to stderr, and
- * add the -p flag to the command
- */
- if (pipeout)
- {
- run_arg ("-p");
- if (!quiet)
- {
- (void) fprintf (stderr, "===================================================================\n");
- if (update_dir[0])
- (void) fprintf (stderr, "Checking out %s/%s\n",
- update_dir, file);
- else
- (void) fprintf (stderr, "Checking out %s\n", file);
- (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path);
- (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs);
- (void) fprintf (stderr, "***************\n");
- }
- }
-
- /* tack on the rcs and maybe the user file */
- run_arg (vers_ts->srcfile->path);
- if (!pipeout)
- run_arg (file);
-
- if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
- (pipeout ? (RUN_NORMAL|RUN_REALLY) : RUN_NORMAL))) == 0)
- {
- if (!pipeout)
- {
- Vers_TS *xvers_ts;
-
- if (cvswrite == TRUE)
- xchmod (file, 1);
-
- /* set the time from the RCS file iff it was unknown before */
- if (vers_ts->vn_user == NULL ||
- strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
- {
- set_time = 1;
- }
- else
- set_time = 0;
-
- xvers_ts = Version_TS (repository, options, tag, date, file,
- force_tag_match, set_time, entries, srcfiles);
- if (strcmp (xvers_ts->options, "-V4") == 0)
- xvers_ts->options[0] = '\0';
- Register (entries, file, xvers_ts->vn_rcs, xvers_ts->ts_user,
- xvers_ts->options, xvers_ts->tag, xvers_ts->date);
-
- /* fix up the vers structure, in case it is used by join */
- if (join_rev1)
- {
- if (vers_ts->vn_user != NULL)
- free (vers_ts->vn_user);
- if (vers_ts->vn_rcs != NULL)
- free (vers_ts->vn_rcs);
- vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs);
- vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs);
- }
-
- /* If this is really Update and not Checkout, recode history */
- if (strcmp (command_name, "update") == 0)
- history_write ('U', update_dir, xvers_ts->vn_rcs, file,
- repository);
-
- freevers_ts (&xvers_ts);
-
- if (!really_quiet)
- {
- if (update_dir[0])
- (void) printf ("U %s/%s\n", update_dir, file);
- else
- (void) printf ("U %s\n", file);
- }
- }
- }
- else
- {
- int old_errno = errno; /* save errno value over the rename */
-
- if (!pipeout && isfile (backup))
- rename_file (backup, file);
-
- error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
- "could not check out %s", file);
-
- retval = retcode;
- }
-
- if (!pipeout)
- (void) unlink_file (backup);
-
- return (retval);
- }
-
- /*
- * Several of the types we process only print a bit of information consisting
- * of a single letter and the name.
- */
- static int
- write_letter (file, letter, update_dir)
- char *file;
- char letter;
- char *update_dir;
- {
- if (!really_quiet)
- {
- if (update_dir[0])
- (void) printf ("%c %s/%s\n", letter, update_dir, file);
- else
- (void) printf ("%c %s\n", letter, file);
- }
- return (0);
- }
-
- /*
- * Do all the magic associated with a file which needs to be merged
- */
- static int
- merge_file (file, repository, entries, vers, update_dir)
- char *file;
- char *repository;
- List *entries;
- Vers_TS *vers;
- char *update_dir;
- {
- char user[PATH_MAX];
- char backup[PATH_MAX];
- int status;
- int retcode = 0;
-
- /*
- * The users currently modified file is moved to a backup file name
- * ".#filename.version", so that it will stay around for a few days
- * before being automatically removed by some cron daemon. The "version"
- * is the version of the file that the user was most up-to-date with
- * before the merge.
- */
- (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
- if (update_dir[0])
- (void) sprintf (user, "%s/%s", update_dir, file);
- else
- (void) strcpy (user, file);
-
- (void) unlink_file (backup);
- copy_file (file, backup);
- xchmod (file, 1);
-
- /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */
- run_setup ("%s%s %s -r%s -r%s", Rcsbin, RCS_RCSMERGE, vers->options,
- vers->vn_user, vers->vn_rcs);
- run_arg (vers->srcfile->path);
- status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
- if (status != 0
- #ifdef HAVE_RCS5
- && status != 1
- #endif
- )
- {
- error (0, status == -1 ? errno : 0,
- "could not merge revision %s of %s", vers->vn_user, user);
- error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
- user, backup);
- rename_file (backup, file);
- return (1);
- }
- /* XXX - Might want to make sure that rcsmerge changed the file */
- if (strcmp (vers->options, "-V4") == 0)
- vers->options[0] = '\0';
- Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options,
- vers->tag, vers->date);
-
- /* fix up the vers structure, in case it is used by join */
- if (join_rev1)
- {
- if (vers->vn_user != NULL)
- free (vers->vn_user);
- vers->vn_user = xstrdup (vers->vn_rcs);
- }
-
- /* possibly run GREP to see if there appear to be conflicts in the file */
- run_setup ("%s -s", GREP);
- run_arg (RCS_MERGE_PAT);
- run_arg (file);
- if (status == 1 ||
- (retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) == 0)
- {
- if (!noexec)
- error (0, 0, "conflicts found in %s", user);
-
- if (!really_quiet)
- (void) printf ("C %s\n", user);
-
- history_write ('C', update_dir, vers->vn_rcs, file, repository);
-
- }
- else if (retcode == -1)
- {
- error (1, errno, "fork failed while examining update of %s", user);
- }
- else
- {
- if (!really_quiet)
- (void) printf ("M %s\n", user);
- history_write ('G', update_dir, vers->vn_rcs, file, repository);
- }
- return (0);
- }
-
- /*
- * Do all the magic associated with a file which needs to be joined
- * (-j option)
- */
- static void
- join_file (file, srcfiles, vers, update_dir)
- char *file;
- List *srcfiles;
- Vers_TS *vers;
- char *update_dir;
- {
- char user[PATH_MAX];
- char backup[PATH_MAX];
- char *rev, *baserev;
- char *options;
- int status;
-
- /* determine if we need to do anything at all */
- if (vers->vn_user == NULL || vers->srcfile == NULL ||
- vers->srcfile->path == NULL)
- {
- return;
- }
-
- /* special handling when two revisions are specified */
- if (join_rev1 && join_rev2)
- {
- rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1);
- if (rev == NULL)
- {
- if (!quiet && date_rev2 == NULL)
- error (0, 0,
- "cannot find revision %s in file %s", join_rev2, file);
- return;
- }
-
- baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1);
- if (baserev == NULL)
- {
- if (!quiet && date_rev1 == NULL)
- error (0, 0,
- "cannot find revision %s in file %s", join_rev1, file);
- free (rev);
- return;
- }
-
- /*
- * nothing to do if:
- * second revision matches our BASE revision (vn_user) &&
- * both revisions are on the same branch
- */
- if (strcmp (vers->vn_user, rev) == 0 &&
- numdots (baserev) == numdots (rev))
- {
- /* might be the same branch. take a real look */
- char *dot = rindex (baserev, '.');
- int len = (dot - baserev) + 1;
-
- if (strncmp (baserev, rev, len) == 0)
- return;
- }
- }
- else
- {
- rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1);
- if (rev == NULL)
- return;
- if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */
- {
- free (rev);
- return;
- }
-
- baserev = RCS_whatbranch (file, join_rev1, srcfiles);
- if (baserev)
- {
- char *cp;
-
- /* we get a branch -- turn it into a revision, or NULL if trunk */
- if ((cp = rindex (baserev, '.')) == NULL)
- {
- free (baserev);
- baserev = (char *) NULL;
- }
- else
- *cp = '\0';
- }
- }
- if (baserev && strcmp (baserev, rev) == 0)
- {
- /* they match -> nothing to do */
- free (rev);
- free (baserev);
- return;
- }
-
- /* OK, so we have a revision and possibly a base revision; continue on */
-
- /*
- * The users currently modified file is moved to a backup file name
- * ".#filename.version", so that it will stay around for a few days
- * before being automatically removed by some cron daemon. The "version"
- * is the version of the file that the user was most up-to-date with
- * before the merge.
- */
- (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
- if (update_dir[0])
- (void) sprintf (user, "%s/%s", update_dir, file);
- else
- (void) strcpy (user, file);
-
- (void) unlink_file (backup);
- copy_file (file, backup);
- xchmod (file, 1);
-
- options = vers->options;
- #ifdef HAVE_RCS5
- if (*options == '\0')
- options = "-kk"; /* to ignore keyword expansions */
- #endif
-
- /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */
- run_setup ("%s%s %s %s%s -r%s", Rcsbin, RCS_RCSMERGE, options,
- baserev ? "-r" : "", baserev ? baserev : "", rev);
- run_arg (vers->srcfile->path);
- status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
- if (status != 0
- #ifdef HAVE_RCS5
- && status != 1
- #endif
- )
- {
- error (0, status == -1 ? errno : 0,
- "could not merge revision %s of %s", rev, user);
- error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
- user, backup);
- rename_file (backup, file);
- }
- free (rev);
- if (baserev)
- free (baserev);
- return;
- }
-
- /*
- * Process the current directory, looking for files not in ILIST and not on
- * the global ignore list for this directory.
- */
- static void
- ignore_files (ilist, update_dir)
- List *ilist;
- char *update_dir;
- {
- DIR *dirp;
- struct direct *dp;
- struct stat sb;
- char *file;
- char *xdir;
-
- /* we get called with update_dir set to "." sometimes... strip it */
- if (strcmp (update_dir, ".") == 0)
- xdir = "";
- else
- xdir = update_dir;
-
- dirp = opendir (".");
- if (dirp == NULL)
- return;
-
- ign_add_file (CVSDOTIGNORE, 1);
- while ((dp = readdir (dirp)) != NULL)
- {
- file = dp->d_name;
- if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
- continue;
- if (findnode (ilist, file) != NULL)
- continue;
- if (lstat (file, &sb) != -1)
- {
- if (S_ISDIR (sb.st_mode))
- continue;
- #ifdef S_IFLNK
- if (S_ISLNK (sb.st_mode))
- continue;
- #endif
- }
- if (ign_name (file))
- continue;
- (void) write_letter (file, '?', xdir);
- }
- (void) closedir (dirp);
- }
-